﻿//////////////////////////////////////////////////////////////////////////////
//
//  Copyright © 1998-2024 Glodon Company Limited.  All rights reserved.
//
//  Use of this software is subject to the terms of the Glodon license
//  agreement provided at the time of installation or download, or which
//  otherwise accompanies this software in either electronic or hard copy form.  
//
//////////////////////////////////////////////////////////////////////////////

// Reviewed
#pragma once
#include "GcmpJournalInterface.h"
#include <functional>

namespace gcmp
{
    class IJournalRecordEventReceiver;
    enum class JournalCommandFlag;
    enum class JournalWidgetFlag;
    class IJournalCheckData;
    class IJournalCommandData;
    class IJournalUiObject;
    class IJournalUiEvent;
    class CommandParameterValue;
    class IJournalUiRecorderAndReplayerCreator;
    class IJournalClient;
    class IJournalMouseData;
    class IJournalKeyData;
    class IJournalReplayEventReceiver;
    class IJournalUiData;
    class Vector3d;
    /// \brief 脚本日志管理者
    ///
    /// 日志对外的唯一接口
    class GCMP_JOURNAL_INTERFACE_EXPORT JournalUtils
    {
    public:
        /// \brief 日志是否在回放状态
        ///
        /// \return true:正在回放;false:不在回放
        static bool IsInReplay();

        /// \brief 停止日志回放
        ///
        /// \return true:停止回放;false:停止回放失败
        static bool StopReplay();

        /// \brief 暂停日志回放
        ///
        /// \return void
        static void PauseReplay();

        /// \brief 继续日志回放
        ///
        /// \return void
        static void ContinueReplay();

        /// \brief 初始化回放器
        ///
        /// \param[in] enableRecord 启用回访记录（默认true支持回访记录）
        /// \param[in] checkLargeFont 检查大字体警告（默认true支持回访检查大字体）
        /// \return 第一次初始化返回true；已经初始化过不会重新初始化，返回false
        static bool InitializeReplay(bool enableRecord, bool checkLargeFont);

        /// \brief 记录ViewActivated视图命令
        ///
        /// \param[in] opICommandData （日志）Command数据
        /// \return 运行结果，true:成功;false:失败
        static bool RecordViewActivated(gcmp::OwnerPtr<IJournalCommandData> opICommandData);

#pragma region 注册
        /// \brief 注册日志录制事件接收者
        ///
        /// 可以派生日志事件接收者来定制Ui对象、事件的忽略录制行为
        /// 比如，当某个对话框不需要日志覆盖时，可以派生一个日志事件接收者，实现忽略行为，并通过这个接口注册进来
        /// 注册进来的多个日志录制事件接收者之间是独立的，注册顺序不会有影响
        /// \param[in] pJournalEventReceiver 日志录制事件接收者
        /// \return 运行结果，true:成功;false:失败
        static bool RegisterJournalRecordEventReceiver(IJournalRecordEventReceiver *pJournalEventReceiver);

        /// \brief 注销日志录制事件接收者
        ///
        /// 查找传入日志录制事件接收者是否已经注册进来，如果找到了，将其从日志录制事件接收者列表中删除
        /// \param[in] pJournalEventReceiver 日志录制事件接收者
        /// \return 运行结果，true:成功;false:失败
        static bool UnregisterJournalRecordEventReceiver(IJournalRecordEventReceiver *pJournalEventReceiver);

        /// \brief 注册日志回放事件接收者
        ///
        /// 可以派生日志回放事件接收者来定制回放行为
        /// 注册进来的多个日志回放事件接收者之间是独立的，注册顺序不会有影响
        /// \param[in] pJournalReplayEventReceiver 日志回放事件接收者
        /// \return void
        static void RegisterJournalReplayEventReceiver(IJournalReplayEventReceiver *pJournalReplayEventReceiver);

        /// \brief 注销日志回放事件接收者
        ///
        /// 查找传入日志回放事件接收者是否已经注册进来，如果找到了，将其从日志回放事件接收者列表中删除
        /// \param[in] pJournalReplayEventReceiver 日志回放事件接收者
        /// \return void
        static void UnregisterJournalReplayEventReceiver(IJournalReplayEventReceiver *pJournalReplayEventReceiver);

        /// \brief 设置或者更新录制指定（日志）Ui层Object和指定（日志）Ui层Event时的预处理方法，若对相同的控件事件重复注册预处理方法，将会报错
        ///
        /// \param[in] pIUiObject （日志）Ui层Object数据
        /// \param[in] pIUiEvent （日志）Ui层Event数据
        /// \param[in] preRecordFunction 预处理方法
        /// \return 运行结果，true:成功;false:失败
        static bool RegisterUiDataPreRecordFunction(const IJournalUiObject *pIUiObject, const IJournalUiEvent *pIUiEvent, std::function<void()> preRecordFunction);

        /// \brief 注销录制指定（日志）Ui层Object和指定（日志）Ui层Event时的预处理方法
        ///
        /// \param[in] pIUiObject （日志）Ui层Object数据
        /// \param[in] pIUiEvent （日志）Ui层Event数据
        /// \return 运行结果，true:成功;false:失败
        static bool UnregisterUiDataPreRecordFunction(const IJournalUiObject *pIUiObject, const IJournalUiEvent *pIUiEvent);

        /// \brief 注册日志录制者和回放者构造器
        ///
        /// 可以派生日志录制者和回放者构造器来定制Ui操作的录制回放逻辑
        /// 注册进来的多个日志录制事件接收者之间是独立的，注册顺序不会有影响
        /// \param[in] opJournalUiRecorderAndReplayerCreator 日志录制者和回放者构造器
        /// \return 运行结果，true:成功;false:失败；构造器声明的控件类名若与日志中其余构造器声明的类名相同，注册会失败
        static bool RegisterJournalUiRecorderAndReplayerCreator(OwnerPtr<IJournalUiRecorderAndReplayerCreator> opJournalUiRecorderAndReplayerCreator);
        /// \brief 日志录制者和回放者构造器是否已注册
        ///
        /// \param[in] widgetClassName 控件类名，根据该类名匹配构造器
        /// \return 运行结果，true:已注册;false:未注册
        static bool IsJournalUiRecorderAndReplayerCreatorRegistered(const std::wstring &widgetClassName);
        /// \brief 注销日志录制者和回放者构造器
        ///
        /// \param[in] widgetClassName 控件类名，根据该类名匹配构造器
        /// \return 运行结果，true:成功;false:失败
        static bool UnregisterJournalUiRecorderAndReplayerCreator(const std::wstring &widgetClassName);
#pragma endregion 注册

        /// \brief 设置日志语句之间回放间隔
        ///
        /// \param[in] interval 日志语句之间回放间隔
        /// \return 运行结果，true:成功;false:失败
        static bool SetReplayInterval(int interval);

        /// \brief 设置不录制指定Id的命令。
        ///
        /// \param[in] commondId 命令Id
        /// \return 运行结果，true:成功;false:失败
        static bool DontRecordCommondOfId(std::wstring commondId);

        /// \brief 同步方式记录命令。
        ///
        /// 调用之后，立即记录命令到脚本中
        /// \param[in] opICommandData （日志）Command数据
        /// \return 运行结果，true:成功;false:失败
        static bool RecordCommand(gcmp::OwnerPtr<gcmp::IJournalCommandData> opICommandData);

#pragma region 异步方式录制命令
        /// \brief 异步方式启动命令记录
        ///
        /// 调用之后，在记录下一个命令之前，所有的操作会暂存在缓存中
        /// 由日志控制结束录制异步命令的时机,用户无法主动结束一个异步命令的记录
        /// 等到所有需要的参数获取后,即调用EndRecordCommand()之后，再将命令及暂存在缓存中的操作写入脚本
        /// \param[in] opICommandData （日志）Command数据
        /// \return 运行结果，true:成功;false:失败
        static bool StartRecordCommand(gcmp::OwnerPtr<gcmp::IJournalCommandData> opICommandData);

        /// \brief 增量添加当前命令的参数
        ///
        /// 异步方式记录命令时有效，同步方式无效
        /// \param[in] key 键
        /// \param[in] value 值
        /// \return 运行结果，true:成功;false:失败
        static bool RecordCommandExtraParameter(const std::wstring &key, const gcmp::CommandParameterValue& value);

        /// \brief 终止当前命令的记录
        ///
        /// 异步方式记录命令时有效，同步方式无效
        /// \return 运行结果，true:成功;false:失败
        static bool AbortRecordCommand();
#pragma endregion 异步方式录制命令

        /// \brief 记录三维坐标系下鼠标操作数据
        ///
        /// \param[in] opMouseData （日志）鼠标操作数据
        /// \param[in] logAll       是否需要将所有的鼠标操作事件记录  true: 记录所有的鼠标操作事件  false ：只会记录执行其他指令前最后一个鼠标操作事件
        /// \return 运行结果，true:成功;false:失败
        static bool RecordMouseData(gcmp::OwnerPtr<IJournalMouseData> opMouseData, bool logAll = false);

        /// \brief 记录键盘操作数据
        ///
        /// \param[in] opKeyData （日志）键盘操作数据
        /// \return 运行结果，true:成功;false:失败
        static bool RecordKeyData(gcmp::OwnerPtr<IJournalKeyData> opKeyData);

        /// \brief 记录键盘操作数据
        ///
        /// \param[in] opUiData （日志）Ui交互操作数据
        /// \param[out] isFilterEvent 是否过滤传入Ui事件，上层Ui需要依据此返回值对Ui事件做过滤，否则影响程序运行
        /// 这么做的原因是：日志内部为了获取一些信息，会多产生一些事件，导致事件队列里会有一些冗余事件，需要做一些过滤
        /// \return 运行结果，true:成功;false:失败
        static bool HandleUiData(gcmp::OwnerPtr<IJournalUiData> opUiData, bool &isFilterEvent);

        /// \brief 设置是否记录所有三维坐标系下鼠标移动数据
        ///
        /// 鼠标移动是一个连续频繁的事件，一般情况下只需要记录最后移动到的位置即可，默认情况不会记录所有移动数据
        /// \param[in] isRecordAllMouseMove 是否记录所有三维坐标系下鼠标移动数据，true:记录所有移动数据;false:不记录所有移动数据，只会记录最后一个移动数据
        /// \return 运行结果，true:成功;false:失败
        static bool SetIsRecordAllMouseMove(bool isRecordAllMouseMove);

        /// \brief 查看是否记录所有三维坐标系下鼠标移动数据
        ///
        /// 鼠标移动是一个连续频繁的事件，一般情况下只需要记录最后移动到的位置即可，默认情况不会记录所有移动数据
        /// \return 是否记录所有三维坐标系下鼠标移动数据，true:记录所有移动数据;false:不记录所有移动数据，只会记录最后一个移动数据
        static bool IsRecordAllMouseMove();

        /// \brief 记录或者比较检查点数据
        ///
        /// 通过这个接口可以在代码中设置一个检查点，并向日志模块传入一个检查点数据，在非回放状态下会将检查点数据记录到脚本中
        /// 回放状态下运行到检查点时，会从脚本中拿出期望的检查点数据，与从接口中传入的检查点数据进行比较，若回放正常这两个数据应该是匹配的
        /// 在一些关键的地方添加检查点可以用于检测关键数据是否正常，比如建模之后可以将构件数量等模型信息记下来，用以检测模型数据是否正常
        /// \param[in] result （日志）结果数据，非回放状态下作为期望值记录到脚本中，回放状态下作为实际值进行比较
        /// \return 非回放状态:(运行结果，true:成功;false:失败);回放状态:(true:期望值与实际值匹配;false:期望值与实际值不匹配)
        static bool LogOrCompareCheckData(gcmp::OwnerPtr<gcmp::IJournalCheckData> result);

        /// \brief 记录或者比较自定义模型验证器的数据
        ///
        /// 通过这个接口可以在代码中设置一个模型验证器检查点，并向日志模块传入一个检查点数据，在非回放状态下会将检查点数据记录到脚本中
        /// 回放状态下运行到检查点时，会计算实时的检查点数据，与记录在基线文件中的检查点数据进行比较，若回放正常这两个数据应该是匹配的
        /// \param[in] validatorName （日志）验证器名字
        /// \param[in] validatorInputString （日志）验证器输入参数字符串
        /// \return 非回放状态:(运行结果，true:成功;false:失败);回放状态:(true:期望值与实际值匹配;false:期望值与实际值不匹配)
        static bool LogOrCompareCustomValidator(const std::wstring& validatorName, const std::wstring& validatorInputString);

        /// \brief 需要回放的字符串
        ///
        /// \param[in] wstr 用于回放的字符串
        /// \return 运行结果，true:成功;false:失败
        static bool ReplayString(const std::wstring& wstr);

        /// \brief 需要回放的脚本文件
        ///
        /// \param[in] filePath 用于回放的脚本文件路径
        /// \return 运行结果，true:成功;false:失败
        static bool ReplayFile(const std::wstring& filePath);

        /// \brief 获取回放的脚本文件的全路径名
        ///
        /// \return 回放的脚本文件的全路径名
        static std::wstring GetReplayFilePath();

#pragma region 视图导航命令
        /// \brief 视图导航所需参数
        struct GCMP_JOURNAL_INTERFACE_EXPORT ViewNavigationParameters
        {
            friend class JournalUtils;
        public:
            ViewNavigationParameters();
            /// \brief 构造函数
            ///
            /// \param[in] viewName 视图名称  cameraType 相机类型 cameraPosition 相机位置  cameraDirection 相机方向
            /// cameraUpDirection 相机上方向  nearDist 近裁剪面距离  farDist 远裁剪面距离 aspectRatio 视口宽高比  orthoWindowHeight 观察窗口高度，仅正交模式有效
            /// fieldOfView 相机张角，仅透视投影有效 windowWidth 画布宽度  windowHeight 画布高度
            ViewNavigationParameters(const std::wstring& viewName , int cameraType,const Vector3d &cameraPosition,
               const Vector3d &cameraDirection ,const Vector3d &cameraUpDirection ,double nearDist , double farDist,
                double aspectRatio , double orthoWindowHeight ,double fieldOfView , int windowWidth , int windowHeight);
        private:
            std::wstring m_wViewName;
            int m_cameraType;
            double m_cameraPositionX;
            double m_cameraPositionY;
            double m_cameraPositionZ ;
            double m_cameraDirectionX;
            double m_cameraDirectionY;
            double m_cameraDirectionZ;
            double m_cameraUpDirectionX;
            double m_cameraUpDirectionY;
            double m_cameraUpDirectionZ;
            double m_cameraNearDistance;
            double m_cameraFarDistance;
            double m_cameraAspectRatio;
            double m_cameraOrthoWindowHeight;
            double m_cameraFieldOfView;
            int m_windowWidth;
            int m_windowHeight;
        };

        /// \brief 记录视图导航命令，用于同步回放相机参数、视图宽高等视图属性
        ///
        /// ViewNavigation是一个连续频繁操作，若每一次操作都记录，会导致记录的数据量剧增，影响可读性和性能
        /// ，多数情况我们只关心最后一次操作的值，只需要将最后一次操作的值记录下来即可，设置immediately为false可实现这个效果
        /// \param[in] parameters 视图导航所需参数
        /// \param[in] immediately 是否立即将ViewNavigation视图命令记录到脚本中，若否，会以覆盖上一次缓存的方式缓存起来
        /// \return 运行结果，true:成功;false:失败
        static bool RecordViewNavigation(const ViewNavigationParameters &parameters, bool immediately);
#pragma endregion 视图导航命令

        static CommandParameterValue GetCommandParameter(const std::wstring &key);

        /// \brief 设置回放状态异常标记
        ///
        static void SetReplayErrorTag();

        /// \brief 获取回放错误状态
        ///
        /// \return 日志回放程序状态，true:有错误;false:没有错误（默认）
        static bool IsReplayError();

        /// \brief 唤醒脚本线程，多用于主线程阻塞的情况，如用户使用了QT事件循环或者windows消息循环
        ///
        /// \return 唤醒成功返回true，否则返回失败
        static  bool RequestWake();

        /// \brief 设置回放时三维视图中鼠标位置
        ///
        static void SetMousePoint(const Vector3d& pos);

        /// \brief 获取回放时三维视图中鼠标位置
        ///
        static Vector3d GetMousePoint();

    };
}


